#include <iostream>

#include "arp_message.hh"
#include "exception.hh"
#include "network_interface.hh"

using namespace std;

//! \param[in] ethernet_address Ethernet (what ARP calls "hardware") address of the interface
//! \param[in] ip_address IP (what ARP calls "protocol") address of the interface
NetworkInterface::NetworkInterface( string_view name,
                                    shared_ptr<OutputPort> port,
                                    const EthernetAddress& ethernet_address,
                                    const Address& ip_address )
  : name_( name )
  , port_( notnull( "OutputPort", move( port ) ) )
  , ethernet_address_( ethernet_address )
  , ip_address_( ip_address )
{
  cerr << "DEBUG: Network interface has Ethernet address " << to_string( ethernet_address ) << " and IP address "
       << ip_address.ip() << "\n";
}

//! \param[in] dgram the IPv4 datagram to be sent
//! \param[in] next_hop the IP address of the interface to send it to (typically a router or default gateway, but
//! may also be another host if directly connected to the same network as the destination) Note: the Address type
//! can be converted to a uint32_t (raw 32-bit IP address) by using the Address::ipv4_numeric() method.
void NetworkInterface::send_datagram( const InternetDatagram& dgram, const Address& next_hop )
{
  // Your code here.
  EthernetFrame msg=EthernetFrame();
  const uint32_t tar_ip=next_hop.ipv4_numeric();
  msg.header.src=ethernet_address_;
  msg.header.type=EthernetHeader::TYPE_IPv4;
  msg.payload=serialize(dgram);

  if(!arp_table_.contains(tar_ip)||arp_table_[tar_ip].second<curr_time_){//mac以太网地址未知
    frame_queue_[tar_ip].first.push(std::move(msg));
    //EthernetFrame arp_req_frame;
    send_arp_request(tar_ip);//发送arp请求
    return;
  }else{
    msg.header.dst=arp_table_[tar_ip].first;
    transmit(msg);//发出数据帧
  }
}

//! \param[in] frame the incoming Ethernet frame
/*
  首先根据 MAC 字段判断是否是发给自己的数据帧，只处理目标为自己或者广播地址的帧。
  然后根据类型字段对有效载荷解析，如果是 ip 包直接把解析包交付给上层队列；
  如果是 arp 包则根据协议头将更新 arp 表，如果收到的是 arp 请求报文，则构造 arp 回复报文回复自己的 mac，
  如果收到的是 arp 回复报文，则查看对应 ip 的待发送消息的队列，发送其中所有的消息。
*/
void NetworkInterface::recv_frame( const EthernetFrame& frame )
{
  // Your code here.
  //
  if(frame.header.dst==ethernet_address_||frame.header.dst==ETHERNET_BROADCAST){
    if(frame.header.type==EthernetHeader::TYPE_ARP){
      ARPMessage msg=ARPMessage();
      if(parse(msg,frame.payload)&&msg.target_ip_address==ip_address_.ipv4_numeric()){
        arp_table_[msg.sender_ip_address]=make_pair(msg.sender_ethernet_address,curr_time_+30000);
        //如果是 ARP 请求，那么就组装对应的 ARPMessage 并发送给请求发送方；
        if(msg.opcode==ARPMessage::OPCODE_REQUEST){
          EthernetFrame response=EthernetFrame();
          make_arp_response(msg,response);
          transmit(response);
        }else{
          //如果是 ARP 响应，那么就将先前缓存的、现在能发送的 IP 数据报全部发送出去。
          queue<EthernetFrame>& ip_queue=frame_queue_[msg.sender_ip_address].first;
          while (!ip_queue.empty())
          {
            ip_queue.front().header.dst=msg.sender_ethernet_address;
            transmit(ip_queue.front());
            ip_queue.pop();
          }
          
        }
      }
    }else if(frame.header.type==EthernetHeader::TYPE_IPv4){
      InternetDatagram msg=InternetDatagram();
      if(parse(msg,frame.payload))
        datagrams_received_.emplace(std::move(msg));
    }
  }
}

//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void NetworkInterface::tick( const size_t ms_since_last_tick )
{
  // Your code here.
  curr_time_+=ms_since_last_tick;
}

void NetworkInterface::send_arp_request(const uint32_t tar_ip){

  EthernetFrame arp_req=EthernetFrame();
  if(frame_queue_.contains(tar_ip)&&frame_queue_[tar_ip].second.has_value()&&frame_queue_[tar_ip].second>=curr_time_)
    return;

  arp_req.header.type=EthernetHeader::TYPE_ARP;//请求类型
  arp_req.header.dst=ETHERNET_BROADCAST;//广播
  arp_req.header.src=ethernet_address_;

  ARPMessage arp_req_msg=ARPMessage();
  arp_req_msg.sender_ip_address=ip_address_.ipv4_numeric();//发送arp请求报文的主机ip地址
  arp_req_msg.sender_ethernet_address=ethernet_address_;//mac地址
  arp_req_msg.opcode=ARPMessage::OPCODE_REQUEST;
  arp_req_msg.target_ip_address=tar_ip;//目标ip

  arp_req.payload=serialize(arp_req_msg);//解包
  transmit(arp_req);
  frame_queue_[tar_ip].second=curr_time_+5000;
}

void NetworkInterface::make_arp_response(const ARPMessage& msg,EthernetFrame& response){
  EthernetHeader& header=response.header;
  header.dst=msg.sender_ethernet_address;
  header.src=ethernet_address_;
  header.type=EthernetHeader::TYPE_ARP;

  ARPMessage arp_res_msg=ARPMessage();
  arp_res_msg.opcode=ARPMessage::OPCODE_REPLY;
  arp_res_msg.sender_ethernet_address=ethernet_address_;
  arp_res_msg.sender_ip_address=ip_address_.ipv4_numeric();
  arp_res_msg.target_ethernet_address=msg.sender_ethernet_address;
  arp_res_msg.target_ip_address=msg.sender_ip_address;
  response.payload=serialize(arp_res_msg);
  return;
}
